package com.huixh.train.business.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.huixh.train.business.domain.SkToken;
import com.huixh.train.business.domain.SkTokenExample;
import com.huixh.train.business.mapper.SkTokenMapper;
import com.huixh.train.business.mapper.cust.SkTokenMapperCust;
import com.huixh.train.business.req.SkTokenQueryReq;
import com.huixh.train.business.req.SkTokenSaveReq;
import com.huixh.train.business.resp.SkTokenQueryResp;
import com.huixh.train.business.service.DailyTrainSeatService;
import com.huixh.train.business.service.DailyTrainStationService;
import com.huixh.train.business.service.SkTokenService;
import com.huixh.train.common.enums.LockPreEnum;
import com.huixh.train.common.resp.PageResult;
import com.huixh.train.utils.random.SnowUtil;
import jakarta.annotation.Resource;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
* @author huixh
*/
@Service
public class SkTokenServiceImpl implements SkTokenService {

    private final Logger logger = LoggerFactory.getLogger(SkTokenServiceImpl.class);
    @Resource
    private SkTokenMapper skTokenMapper;
    @Resource
    private DailyTrainSeatService dailyTrainSeatService;
    @Resource
    private DailyTrainStationService dailyTrainStationService;
    @Resource
    private SkTokenMapperCust serviceCustService;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private SnowUtil snowUtil;

    @Override
    public void save(SkTokenSaveReq skTokenSaveReq) {
        DateTime now = DateTime.now();
        SkToken skToken = BeanUtil.copyProperties(skTokenSaveReq, SkToken.class);
        if (ObjectUtil.isNull(skToken.getId())) {
            skToken.setId(snowUtil.getSnowflakeToLong());
            skToken.setCreateTime(now);
            skToken.setUpdateTime(now);
            skTokenMapper.insert(skToken);
        } else {
            skToken.setUpdateTime(now);
            skTokenMapper.updateByPrimaryKey(skToken);
        }
    }

    @Override
    public PageResult <List<SkTokenQueryResp>> queryList(SkTokenQueryReq skTokenQueryReq) {
        SkTokenExample skTokenExample = new SkTokenExample();
        skTokenExample.setOrderByClause("id desc");
        SkTokenExample.Criteria criteria = skTokenExample.createCriteria();

        logger.info("查询页码：{}", skTokenQueryReq.getPageNum());
        logger.info("每页条数：{}", skTokenQueryReq.getPageSize());
        PageHelper.startPage(skTokenQueryReq.getPageNum(), skTokenQueryReq.getPageSize());
        List<SkToken> skTokenList = skTokenMapper.selectByExample(skTokenExample);

        PageInfo<SkToken> pageInfo = new PageInfo<>(skTokenList);
        logger.info("总行数：{}", pageInfo.getTotal());
        logger.info("总页数：{}", pageInfo.getPages());

        List<SkTokenQueryResp> list = BeanUtil.copyToList(skTokenList, SkTokenQueryResp.class);
        return PageResult.success(pageInfo.getTotal(), list);
    }

    @Override
    public void delete(Long id) {
        skTokenMapper.deleteByPrimaryKey(id);
    }

    /**
     * 生成令牌
     *
     * @param date      时间
     * @param trainCode 车次
     */
    @Override
    public void genDaily(Date date, String trainCode) {
        logger.info("删除日期【{}】车次【{}】的令牌记录", DateUtil.formatDate(date), trainCode);
        SkTokenExample tokenExample = new SkTokenExample();
        SkTokenExample.Criteria criteria = tokenExample.createCriteria();
        criteria.andTrainCodeEqualTo(trainCode)
                .andDateEqualTo(date);
        skTokenMapper.deleteByExample(tokenExample);

        DateTime now = DateTime.now();
        SkToken skToken = new SkToken();
        skToken.setDate(date);
        skToken.setTrainCode(trainCode);
        skToken.setId(snowUtil.getSnowflakeToLong());
        skToken.setUpdateTime(now);
        skToken.setCreateTime(now);

        Long countSeat = dailyTrainSeatService.countSeat(date, trainCode);
        logger.info("当前车次【{}】一共存在【{}】座位",trainCode,countSeat);
        Long countStatuin = dailyTrainStationService.countByTrainCodeAndDate(date, trainCode);

        logger.info("当前车次【{}】存在【{}】个站点",trainCode,countStatuin);

        long skTokenCount = countStatuin * countSeat;
        skToken.setCount((int) skTokenCount);

        skTokenMapper.insert(skToken);
        logger.info("为新增车次【{}】增加【{}】个令牌",trainCode,skTokenCount);
    }

    /**
     * 验证令牌
     *
     * @param date 日期
     * @param trainCode 车次
     * @return 成功?
     */
    @Override
    @SentinelResource("SkTokenServiceImpl-verificationToken")
    public Boolean verificationToken(Date date, String trainCode,Long memberId,Integer step) {
        logger.info("会员【{}】：获取日期： 【{}】 车次：【{}】 的令牌开始",memberId,DateUtil.formatDate(date),trainCode);
        String skTokenCountKey = LockPreEnum.LOCK_PRE_SK_TOKEN_COUNTER+ DateUtil.formatDate(date)  + trainCode;
        Object skTokenCountValue = redisTemplate.opsForValue().get(skTokenCountKey);
        if (skTokenCountValue!=null) {
            logger.info("缓存中有该车次令牌的数量为： 【{}】", skTokenCountValue);
            Long skTokenCount = redisTemplate.opsForValue().decrement(skTokenCountKey,1L);
            assert skTokenCount != null;
            if (skTokenCount < 0){
                // 令牌不足
                return false;
            } else {
                logger.info("获取令牌后，令牌余数【{}】",skTokenCount);
                redisTemplate.expire(skTokenCountKey,60,TimeUnit.SECONDS);
                if (skTokenCount % 5 == 0) {
                    serviceCustService.decrease(date, trainCode,5);
                }
                return true;
            }
        } else {
            logger.info("缓存中没有该车次令牌： 【{}】", skTokenCountKey);
            SkTokenExample tokenExample = new SkTokenExample();
            tokenExample.createCriteria().andDateEqualTo(date).andTrainCodeEqualTo(trainCode);
            List<SkToken> skTokenList = skTokenMapper.selectByExample(tokenExample);
            if (CollUtil.isEmpty(skTokenList)){
                logger.info("数据库中没有该车次令牌： 【{}】", skTokenList);
                return false;
            }
            SkToken skToken = skTokenList.get(0);
            if (skToken.getCount()<=0) {
                logger.info("数据库中该车次令牌余数为0： 【{}】", skToken);
                return false;
            }

            Integer count = skToken.getCount()-1;
            skToken.setCount(count);
            logger.info("将数据库中该车次令牌存到缓存中，令牌key:【{}】余数为： 【{}】",skTokenCountKey, count);
            redisTemplate.opsForValue().set(skTokenCountKey,count,60,TimeUnit.SECONDS);
            return true;
        }
    }

    /**
     * 验证令牌
     *
     * @param date 日期
     * @param trainCode 车次
     * @return 成功?
     */
    @Override
    public Boolean verificationToken(Date date, String trainCode,Long memberId) {
        return verificationToken(date, trainCode, memberId,1);
    }
}